/**
 * [贝叶斯分类器](http://en.wikipedia.org/wiki/Naive_Bayes_classifier)
 *
 * 这是一个朴素贝叶斯分类器，它接受
 * 仅包含一级嵌套的对象。
 *
 * @class
 * @example
 * var bayes = new BayesianClassifier();
 * bayes.train({
 *   species: 'Cat'
 * }, 'animal');
 * var result = bayes.score({
 *   species: 'Cat'
 * })
 * // 结果
 * // {
 * //   animal: 1
 * // }
 */
class BayesianClassifier {
    /*:: totalCount: number */
    /*:: data: Object */
    constructor() {
        // 当前模型中已分类的项目总数
        this.totalCount = 0;
        // 记录所有已分类的项目
        this.data = {};
    }

    /**
     * 使用新项目训练分类器，该项目具有
     * JavaScript 字面量键值对的单维属性。
     *
     * @param {Object} item 具有一级属性的对象
     * @param {string} category 该项目所属的类别
     * @return {undefined} 将项目添加到分类器中
     */
    train(item, category) {
        // 如果数据对象中尚未存储该类别的数据，
        // 则为其创建一个新的对象。
        if (!this.data[category]) {
            this.data[category] = {};
        }

        // 遍历项目的每个键。
        for (const k in item) {
            const v = item[k];
            // 初始化嵌套对象 `data[category][k][item[k]]`，
            // 其中的键初始值均为 0。
            if (this.data[category][k] === undefined) {
                this.data[category][k] = {};
            }
            if (this.data[category][k][v] === undefined) {
                this.data[category][k][v] = 0;
            }

            // 递增该键值对的计数。
            this.data[category][k][v]++;
        }

        // 递增已分类的项目总数
        this.totalCount++;
    }

    /**
     * 计算该项目匹配所有可能类别的概率，
     * 依据其属性进行分类评分。
     *
     * @param {Object} item 格式与 train 方法输入一致的对象
     * @returns {Object} 返回该项目属于各个类别的概率分布
     */
    score(item) {
        // 初始化一个对象来存储每个类别的概率。
        const odds = {};
        let category;
        // 遍历项目的每个键，
        // 然后遍历所有已训练的类别。
        for (const k in item) {
            const v = item[k];
            for (category in this.data) {
                // 为当前类别创建一个对象，用于存储键值组合的概率。
                odds[category] = {};

                // 如果该类别中没有该属性，则该属性的概率为 0；
                // 如果该类别包含该属性，则计算该属性的出现频率，
                // 其值为该属性在类别中的出现次数除以总样本数。
                if (this.data[category][k]) {
                    odds[category][k + "_" + v] =
                        (this.data[category][k][v] || 0) / this.totalCount;
                } else {
                    odds[category][k + "_" + v] = 0;
                }
            }
        }

        // 初始化一个对象，用于存储每个类别的总概率。
        const oddsSums = {};

        for (category in odds) {
            // 计算每个类别的所有键值组合概率之和，
            // 不存在的类别不会影响最终得分。
            oddsSums[category] = 0;
            for (const combination in odds[category]) {
                oddsSums[category] += odds[category][combination];
            }
        }

        return oddsSums;
    }
}

export default BayesianClassifier;
